﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace Roton.Engine.Memory
{
    /// <summary>
    /// Base class that contains basic functionality for memory pointer emulation.
    /// </summary>
    abstract public class Pointer<T>
    {
        /// <summary>
        /// Encoding for string conversion.
        /// </summary>
        static protected Encoding Encoding = Encoding.GetEncoding(437);

        /// <summary>
        /// Create a pointer that references memory in a block at a specific offset.
        /// </summary>
        public Pointer(Block block, int offset)
        {
            this.Block = block;
            this.Offset = offset;
        }

        /// <summary>
        /// Get or set a byte at a relative offset.
        /// </summary>
        public byte this[int index]
        {
            get
            { 
                Position = Offset + index;
                return Reader.ReadByte();
            }
            set
            {
                Position = Offset + index;
                Writer.Write(value);
            }
        }

        /// <summary>
        /// Block that this pointer references.
        /// </summary>
        private Block Block
        {
            get;
            set;
        }

        /// <summary>
        /// Dereference the pointer.
        /// </summary>
        public void Free()
        {
            this.Block = null;
            this.Offset = -1;
        }

        /// <summary>
        /// Offset in the memory block that this pointer references.
        /// </summary>
        public int Offset
        {
            get;
            private set;
        }

        /// <summary>
        /// Position of the memory block stream.
        /// </summary>
        protected long Position
        {
            get { return Block.Position; }
            set { Block.Position = value; }
        }

        /// <summary>
        /// Read a value via the pointer.
        /// </summary>
        virtual public T Read() { return Read(0); }

        /// <summary>
        /// Read a value via the pointer, at a relative index.
        /// </summary>
        abstract public T Read(int index);

        /// <summary>
        /// Reader for stream access.
        /// </summary>
        protected BinaryReader Reader
        {
            get;
            private set;
        }

        /// <summary>
        /// Get or set a value via the pointer.
        /// </summary>
        public T Value
        {
            get { return Read(); }
            set { Write(value); }
        }

        /// <summary>
        /// Write a value via the pointer.
        /// </summary>
        virtual public void Write(T value) { Write(0, value); }

        /// <summary>
        /// Write a value via the pointer, at a relative index.
        /// </summary>
        abstract public void Write(int index, T value);

        /// <summary>
        /// Writer for stream access.
        /// </summary>
        protected BinaryWriter Writer
        {
            get;
            private set;
        }
    }

    public class BoolPointer : Pointer<Boolean>
    {
        public BoolPointer(Block block, int offset) : base(block, offset) { }

        public override bool Read(int index)
        {
            Position = Offset + index;
            return (Reader.ReadByte() != 0);
        }

        public override void Write(int index, bool value)
        {
            Position = Offset + index;
            Writer.Write((byte)(value ? 0x01 : 0x00));
        }
    }

    public class BytePointer : Pointer<Byte>
    {
        public BytePointer(Block block, int offset) : base(block, offset) { }

        /// <summary>
        /// Read a byte at the specified index.
        /// </summary>
        public override Byte Read(int index)
        {
            Position = Offset + index;
            return Reader.ReadByte();
        }

        /// <summary>
        /// Read a sequence of bytes at the specified index.
        /// </summary>
        public Byte[] Read(int index, int count)
        {
            Position = Offset + index;
            return Reader.ReadBytes(count);
        }

        /// <summary>
        /// Write a byte at the specified index.
        /// </summary>
        public override void Write(int index, Byte value)
        {
            Position = Offset + index;
            Writer.Write(value);
        }

        /// <summary>
        /// Write a sequence of bytes at the specified index.
        /// </summary>
        public void Write(int index, Byte[] value)
        {
            Position = Offset + index;
            Writer.Write(value);
        }
    }

    public class ShortPointer : Pointer<Int16>
    {
        public ShortPointer(Block block, int offset) : base(block, offset) { }

        /// <summary>
        /// Read a 16-bit value at the specified index.
        /// </summary>
        public override Int16 Read(int index)
        {
            Position = Offset + (index << 1);
            return Reader.ReadInt16();
        }

        /// <summary>
        /// Write a 16-bit value at the specified index.
        /// </summary>
        public override void Write(int index, Int16 value)
        {
            Position = Offset + (index << 1);
            Writer.Write(value);
        }
    }

    public class IntPointer : Pointer<Int32>
    {
        public IntPointer(Block block, int offset) : base(block, offset) { }

        /// <summary>
        /// Read a 32-bit value at the specified index.
        /// </summary>
        public override Int32 Read(int index)
        {
            Position = Offset + (index << 2);
            return Reader.ReadInt32();
        }

        /// <summary>
        /// Write a 32-bit value at the specified index.
        /// </summary>
        public override void Write(int index, Int32 value)
        {
            Position = Offset + (index << 2);
            Writer.Write(value);
        }
    }

    public class StringPointer : Pointer<string>
    {
        public StringPointer(Block block, int offset) : base(block, offset) { }

        /// <summary>
        /// Read the length of the string.
        /// </summary>
        public int Length
        {
            get
            {
                Position = Offset;
                return Reader.ReadByte();
            }
        }

        /// <summary>
        /// Read a string.
        /// </summary>
        public override string Read()
        {
            return Encoding.GetString(ReadBytes());
        }

        /// <summary>
        /// Read a character at the specified index.
        /// </summary>
        public override string Read(int index)
        {
            Position = Offset + index + 1;
            return Encoding.GetString(Reader.ReadBytes(1));
        }

        /// <summary>
        /// Read the string as a byte array.
        /// </summary>
        public byte[] ReadBytes()
        {
            Position = Offset;
            int length = Length; // auto advance 1 byte here
            return Reader.ReadBytes(length);
        }

        /// <summary>
        /// Read a byte from the string at the specified index.
        /// </summary>
        public byte[] ReadBytes(int index, int length)
        {
            Position = Offset + index + 1;
            return Reader.ReadBytes(length);
        }

        /// <summary>
        /// Write a string.
        /// </summary>
        public override void Write(string value)
        {
            WriteBytes(Encoding.GetBytes(value.Substring(0, value.Length & 0xFF)));
        }

        /// <summary>
        /// Write a string at the specified index.
        /// </summary>
        public override void Write(int index, string value)
        {
            Position = Offset + index + 1;
            Writer.Write(Encoding.GetBytes(value.Substring(index, 1)));
        }

        /// <summary>
        /// Write a raw sequence of bytes at index 0.
        /// </summary>
        public void WriteBytes(byte[] value)
        {
            byte length = (byte)(value.Length & 0xFF);
            Position = Offset;
            Writer.Write(length);
            Writer.BaseStream.Write(value, 0, length);
        }

        /// <summary>
        /// Write a raw sequence of bytes at the specified index.
        /// </summary>
        public void WriteBytes(int index, byte[] value)
        {
            byte length = (byte)(value.Length & 0xFF);
            Position = Offset + index;
            Writer.BaseStream.Write(value, 0, length);
        }
    }
}
